﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using Utilities;

namespace SilverLightStreamingInterface
{
    /// <summary>
    /// Keeps track of the state of files - have they been uploaded yet? Access to different
    /// directories is thread safe (uses the filesystem), but only one directory's files per thread.
    /// </summary>
    public class FileUploadStateCache
    {
        /// <summary>
        /// Where we can write our cache files and directory structure.
        /// </summary>
        public DirectoryInfo CacheRootDirectory { get; set; }

        public FileUploadStateCache()
        {
            CacheRootDirectory = new DirectoryInfo(Path.GetTempPath() + "\\File Upload Cache State");
        }

        public class DirectoryCache
        {
            private FileInfo _cacheFile;

            protected DirectoryCache(FileInfo cacheFile)
            {
                _cacheFile = cacheFile;
                LoadCache();
            }

            /// <summary>
            /// Resets the cache for this directory -- marking everything as "unread".
            /// </summary>
            public void Clear()
            {
                lock (this)
                {
                    _marked.Clear();
                    WriteCacheCache();
                }
            }

            /// <summary>
            /// In memory version of the file cache.
            /// </summary>
            private Dictionary<string, DateTime> _marked = null;

            /// <summary>
            /// Mark a file. The time stamp is the current timestamp (i.e NOW).
            /// </summary>
            /// <param name="filename">Name of the file (not the path)</param>
            public void MarkFile(string filename)
            {
                MarkFile(filename, DateTime.Now);
            }

            /// <summary>
            /// Mark a file with the given date/time
            /// </summary>
            /// <param name="filename"></param>
            /// <param name="dateTime"></param>
            public void MarkFile(string filename, DateTime dateTime)
            {
                lock (this)
                {
                    _marked[filename] = dateTime;
                    UpdateCacheWithOneFile(filename, dateTime);
                }
            }

            /// <summary>
            /// Mark a file given the fileinfo. NOTE: We ignore the directory!!
            /// </summary>
            /// <param name="file">Only the file name and last modification time</param>
            public void MarkFile(FileInfo file)
            {
                MarkFile(file.Name, file.LastWriteTime);
            }

            /// <summary>
            /// Mark a file.
            /// </summary>
            /// <param name="file">Only the Name is used, directory is ignored.</param>
            /// <param name="time"></param>
            public void MarkFile(FileInfo file, DateTime time)
            {
                MarkFile(file.Name, time);
            }

            /// <summary>
            /// Return true if the file is marked
            /// </summary>
            /// <param name="filename">Name of the file (not the path)</param>
            /// <returns></returns>
            public bool IsFileMarked(string filename)
            {
                lock (this)
                {
                    return _marked.ContainsKey(filename);
                }
            }

            /// <summary>
            /// Return true if the file is marked.
            /// </summary>
            /// <param name="file">Only the Name parameter is used</param>
            /// <returns></returns>
            public bool IsFileMarked(FileInfo file)
            {
                return IsFileMarked(file.Name);
            }

            /// <summary>
            /// Return the time of the marking.
            /// </summary>
            /// <param name="file">File to get the timestamp for. Only the Name is used.</param>
            /// <returns></returns>
            public DateTime TimeOfMark(FileInfo file)
            {
                return TimeOfMark(file.Name);
            }

            /// <summary>
            /// Returns when this file was marked.
            /// </summary>
            /// <param name="fname"></param>
            /// <returns></returns>
            public DateTime TimeOfMark(string fname)
            {
                lock (this)
                {
                    if (!_marked.ContainsKey(fname))
                    {
                        return new DateTime();
                    }
                    else
                    {
                        return _marked[fname];
                    }
                }
            }

            /// <summary>
            /// What we write out to our helper XML file.
            /// </summary>
            public class CacheData
            {
                public string _filename;
                public DateTime _stamp;

                private const string _seperator = " ##### ";

                public override string ToString()
                {
                    return _filename + _seperator + _stamp.ToBinary();
                }

                public static CacheData LoadFromLine(string line)
                {
                    int loc = line.IndexOf(_seperator);
                    if (loc < 0)
                    {
                        throw new SilverLightStreamException("Cached file line is of wrong format: " + line);
                    }
                    string fname = line.Substring(0, loc);
                    string date = line.Substring(loc + _seperator.Length);
                    date = date.Trim();
                    return new CacheData() { _filename = fname, _stamp = DateTime.FromBinary(Convert.ToInt64(date)) };
                }
            }

            /// <summary>
            /// Load the cache from the file
            /// </summary>
            private void LoadCache()
            {
                _marked = new Dictionary<string, DateTime>();
                try
                {
                    var dt = from cdt in _cacheFile.GetLineIterator()
                             select CacheData.LoadFromLine(cdt);

                    foreach (var cdt in dt)
                    {
                        _marked[cdt._filename] = cdt._stamp;
                    }
                }
                catch
                {
                }
            }

            /// <summary>
            /// Save the cache back out.
            /// </summary>
            private void WriteCacheCache()
            {
                var assimple = from k in _marked.Keys
                               select new CacheData() { _filename = k, _stamp = _marked[k] };

                if (!_cacheFile.Directory.Exists)
                {
                    _cacheFile.Directory.Create();
                }
                using (TextWriter wr = _cacheFile.CreateText())
                {
                    foreach (var cdt in assimple)
                    {
                        wr.WriteLine(cdt.ToString());
                    }
                    wr.Close();
                }
            }

            /// <summary>
            /// Just adding a single line - so append it.
            /// </summary>
            /// <param name="fname"></param>
            /// <param name="time"></param>
            private void UpdateCacheWithOneFile(string fname, DateTime time)
            {
                using (TextWriter wr = _cacheFile.CreateAppendForce())
                {
                    wr.WriteLine(new CacheData() { _filename = fname, _stamp = time }.ToString());
                    wr.Close();
                }
            }
        }

        private class MyDirectoryCache : DirectoryCache
        {
            public MyDirectoryCache(FileInfo cFile)
                : base(cFile)
            {
            }
        }

        /// <summary>
        /// Get the cache for a particular directory.
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <returns></returns>
        public DirectoryCache GetCacheForDirectory(string fileInfo)
        {
            string normalizedpath = CacheRootDirectory + "\\" + fileInfo.Replace(':', '\\') + "\\dircache.xml";

            return new MyDirectoryCache(new FileInfo(normalizedpath));
        }

        /// <summary>
        /// Return the cache data for a particular file.
        /// </summary>
        /// <param name="dir"></param>
        /// <returns></returns>
        public DirectoryCache GetCacheForDirectory(DirectoryInfo dir)
        {
            return GetCacheForDirectory(dir.FullName);
        }

        public void ClearCacheStatus(DirectoryInfo dir)
        {
            ClearCacheStatus(dir.FullName);
        }

        /// <summary>
        /// Check the status of the cache.
        /// </summary>
        /// <param name="dirpath"></param>
        public void ClearCacheStatus(string dirpath)
        {
            DirectoryInfo normalizedPath = new DirectoryInfo(CacheRootDirectory + "\\" + dirpath.Replace(':', '\\'));
            if (normalizedPath.Exists)
            {
                normalizedPath.Delete(true);
            }
            
        }

    }
}
